/*
 * http_client.c
 *
 *  Created on: Mar 16, 2015
 *      Author: song
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <pthread.h>
#include <unistd.h>
#include <sys/time.h>
#include <assert.h>

#include "pkg_vehicle.h"
#include "http_client.h"
#include "http_lib.h"

#define MAXBUF 512

#define REGIST_PREFFIX "/toll-gate/home/regist?deviceId="
#define HEARTBEAT_PREFFIX "/toll-gate/home/heartBeat?deviceId="
#define INFO_PREFFIX "/toll-gate/home/upload?deviceId="

static char http_server_uri[MAXBUF];
static char g_device_id[MAXBUF];

static int http_heart_cycle = 100;
static int g_reset_flag = 1;

static pthread_t g_httpthread_tid = (pthread_t) -1;

struct drive_info {
	char time[32]; //yyyymmddHHMMSS
	char localtion[128];
	unsigned short speed;
	char status[10]; // "PC_9805"
};

struct vehicle_info {
	char type[4];	// "X99"
	char color;	// 'A'
	unsigned short length; // in cm
	char notype[32]; // The longest content is "MotorcycleTemporaryEntry"
	char no[16];	// plate ""
	char nocolor[8]; //  "Other" "White" "Yellow" "Blue" "Black"

};

struct analyse_result {
	char deviceId[64];
	char time[32];	// yyyymmddHHMMSSxxx
	char fhistory;
	char type[64]; // "EventSnapshot"  TimingSnapshot/ManualSnapshot/EventSnapshot
	char description[64];

	char* vehicle; // 车辆图片
	int vlen;
	char* feature; // 特征图片
	int flen;

	struct vehicle_info vinfo;
	struct drive_info dinfo;

	struct analyse_result * next;
};

static void release_result(struct analyse_result *ret)
{
	if (ret == NULL)
		return;

	if (ret->vehicle != NULL)
		free(ret->vehicle);

	if (ret->feature != NULL)
		free(ret->feature);

	free(ret);
}


static struct analyse_result * g_send_queue = NULL;
static pthread_mutex_t g_queue_lock = PTHREAD_MUTEX_INITIALIZER;

static void clear_queue()
{
	pthread_mutex_lock(&g_queue_lock);
	struct analyse_result * temp = NULL;
	while(g_send_queue != NULL) {
		temp = g_send_queue->next;
		release_result(g_send_queue);
		g_send_queue = temp;
	}
	pthread_mutex_unlock(&g_queue_lock);
}

static void addto_queue(struct analyse_result *ret)
{
	pthread_mutex_lock(&g_queue_lock);
	if (g_send_queue == NULL) {
		g_send_queue = ret;
	} else {
		struct analyse_result * temp = g_send_queue;
		while (temp->next != NULL) {
			temp = temp->next;
		}
		temp->next = ret;
	}
	pthread_mutex_unlock(&g_queue_lock);

}

static struct analyse_result *getfrom_queue()
{
	struct analyse_result *ret = NULL;
	pthread_mutex_lock(&g_queue_lock);
	if (g_send_queue != NULL) {
		ret = g_send_queue;
		g_send_queue = ret->next;
		ret->next = NULL;
	}
	pthread_mutex_unlock(&g_queue_lock);
	return ret;
}

/*
 * @return	0,successful  -1,failed
 */
static int do_regist()
{
	int ret = -1;
	char url[MAXBUF];
	sprintf(url, "%s%s%s", http_server_uri, REGIST_PREFFIX, g_device_id);
	char *fd = NULL;
	http_parse_url(url, &fd);

	char reg_data[] = "deviceIp:0.0.0.0\015\012"
			"msgPort:0\015\012"
			"company:huaan\015\012"
			"videoRegFlag:false\015\012";
	int len = (int) strlen(reg_data);
	char *response = (char *) calloc(1024 * 4, 1);
	if (response == NULL)
		return -1;
	http_retcode r = http_post(fd, reg_data, len, 0, "text/xml", response,
			1024 * 4);
//	printf("retcode for POST: %d\n", r);
//	printf("response is: %s\n", response);
	if (r == OK200) {
		ret = 0;
		const char *heart_cycle = "<HeartCycle>";
		const char * pos = strstr(response, heart_cycle);
		pos += strlen(heart_cycle);
		http_heart_cycle = atoi(pos);
		printf("heart cycle is %d\n", http_heart_cycle);
	} else {
		ret = -1;
	}
	free(response);
	return ret;
}

static void do_heartbeat()
{
//	printf("Heart beating ...\n");
	char url[MAXBUF];
	sprintf(url, "%s%s%s", http_server_uri, HEARTBEAT_PREFFIX, g_device_id);
	char *fd = NULL;
	http_parse_url(url, &fd);

	char heartbeat[] = "<Message>"
			"<Device StateCode=\"0\">"
			"<Status Name=\"Detector\" Value=\"1\" />"
			"<Status Name=\"Flash\" Value=\"1\" />"
			"<Status Name=\"Video\" Value=\"1\" />"
			"</Device>"
			"</Message>";
	int len = strlen(heartbeat);
	http_post(fd, heartbeat, len, 0, "text/xml", NULL, 0);
}

const char * content_disposition_formater[] =
		{
				"Content-Disposition: form-data; name=\"vehicleImage\"; filename=\"vehicleImage.jpg\"\015\012",
				"Content-Type: image/jpeg\015\012", "\015\012" };

#define DISPOSITION1 "Content-Disposition: form-data; name=\"analyseResult\""
#define DISPOSITION2 "Content-Disposition: form-data; name=\"vehicleImage\"; filename=\"vehicleImage.jpg\"\015\012Content-Type: image/jpeg\015\012"

const char * vehicle_content_formater[] =
		{
				"%s\015\012",	// boundary
				"%s\015\012\015\012",	// Content-Disposition
				"<analyseResult>",
				"<vehicleInfo vehicleType=\"%s\" vehicleColor=\"%c\" vehicleLength=\"%d\" ",
				"vehicleNoType=\"%s\" vehicleNo=\"%s\" vehicleNoColor=\"%s\">",
				"<driveInfo time=\"%s\" location=\"%s\" speed=\"%d\" status=\"%s\" />",
				"</vehicleInfo>", "</analyseResult>",
				"\015\012", "%s\015\012", // boundary
				"%s\015\012" // Content-Disposition
		};

#define BOUNDARY "-----------1A2B3C4D5E6FF6E5D4C3B2A1"

const char * vehicle_header_formater[] = { "POST %s HTTP/1.1\015\012",
		"Content-Type: multipart/form-data; boundary=%s\015\012",
		"Content-Length: %d\015\012", "Cache-Control: no-cache\015\012",
		"Pragma: no-cache\015\012",
		"Accept: text/html, image/jpeg\015\012",
		"Connection: keep-alive\015\012"};

static void pack_header(char *header, int content_len)
{
	char *temp = header;
	int n, i = 1;
	while (i < sizeof(vehicle_header_formater) / sizeof(char *)) {
		switch (i) {
		case 1:
			n = sprintf(temp, vehicle_header_formater[i], BOUNDARY);
			break;
		case 2:
			n = sprintf(temp, vehicle_header_formater[i],
					content_len);
			break;
		default:
			n = sprintf(temp, "%s", vehicle_header_formater[i]);
			break;
		}
		temp += n;
		i++;
	}
}

static int pack_content(char *buffer, struct analyse_result *ret)
{
	char *temp = buffer;
	int n = 0, i = 0;
	while (i < sizeof(vehicle_content_formater) / sizeof(char *)) {
		switch (i) {
		case 0:
		case 9:
			n = sprintf(temp, vehicle_content_formater[i],
			BOUNDARY);
			break;
		case 1:
			n = sprintf(temp, vehicle_content_formater[i],
			DISPOSITION1);
			break;
		case 10:
			n = sprintf(temp, vehicle_content_formater[i],
			DISPOSITION2);
			break;
		case 3:
			// "<vehicleInfo vehicleType=\"%s\" vehicleColor=\"%s\" vehicleLength=\"%d\"",
			n = sprintf(temp, vehicle_content_formater[i],
					ret->vinfo.type, ret->vinfo.color,
					ret->vinfo.length);
			break;
		case 4:
			// "vehicleNoType=\"%s\" vehicleNo=\"%s\" vehicleNoColor=\"%s\">",
			n = sprintf(temp, vehicle_content_formater[i],
					ret->vinfo.notype, ret->vinfo.no,
					ret->vinfo.nocolor);
			break;
		case 5:
			// "<driveInfo time=\"%s\" location=\"%s\" speed=\"%d\" status=\"%s\" />"
			n = sprintf(temp, vehicle_content_formater[i],
					ret->dinfo.time, ret->dinfo.localtion,
					ret->dinfo.speed, ret->dinfo.status);
			break;
		default:
			n = sprintf(temp, "%s", vehicle_content_formater[i]);
			break;
		}
		temp += n;
		i++;
	}
	return (int) strlen(buffer);
}

static void do_send(struct analyse_result *ret)
{
	char url[MAXBUF];
	sprintf(url, "%s%s%s&type=%s&time=%s", http_server_uri, INFO_PREFFIX,
			ret->deviceId, ret->type, ret->time);
	char *fd = NULL;
	http_parse_url(url, &fd);

	int dlen;
	char* mem = (char *) malloc(4096 + 1024 + ret->vlen + 1024 + ret->flen);
	if (mem == NULL) {
		printf("malloc for packet buffer failed.\n");
		return;
	}
	char * header = mem;
	char *data = mem + 4096;

	dlen = pack_content(data, ret);

	memcpy(data + dlen, ret->vehicle, ret->vlen);
	dlen += ret->vlen;

	memcpy(data + dlen, "\015\012", 2);
	memcpy(data + dlen + 2, BOUNDARY, strlen(BOUNDARY));

	dlen += (2 + strlen(BOUNDARY));

	pack_header(header, dlen);
//	printf(" ****** PACKET ******* \n%s%s", header, data);
//	printf("CONTENT: %s", data);
	if (OK200 != http_post_withheader(fd, header, data, dlen)) {
		clear_queue();
	}

	free(mem);
}

static inline const char *timeval2str(const struct timeval time)
{
	static char str[64];
	struct tm *t = localtime(&(time.tv_sec));
	memset(str, 0, 64);
	sprintf(str, "%.4d%.2d%.2d%.2d%.2d%.2d%.3ld", t->tm_year + 1900,
			t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min,
			t->tm_sec, time.tv_usec/1000);
	return str;
}

/*
 * @brief	将车辆信息封装成协议特定结构体
 * 		注意这里使用了一些 char*指向了info中的内容
 * 		在使用这个结构体期间，外部传入的info不能释放
 */
static struct analyse_result * pack_result(const struct pkg_vehicle *info)
{
	const char *timestr = NULL;
	struct timeval tv;
	gettimeofday(&tv, NULL);
	timestr = timeval2str(tv);

	struct analyse_result * ret = (struct analyse_result *) malloc(
			sizeof(struct analyse_result));

	if (ret == NULL)
		return NULL;

	snprintf(ret->deviceId, sizeof(ret->deviceId), "%s", info->device_id);
	snprintf(ret->time, sizeof(ret->time), "%s", timestr);
	ret->fhistory = 0;
	snprintf(ret->type, sizeof(ret->type), "EventSnapshot");
	snprintf(ret->description, sizeof(ret->description), "descForResult");

	if (info->img_num > 0 && info->img_len[0] > 0) {
		ret->vehicle = (char *) malloc(info->img_len[0]);
		if (ret->vehicle == NULL) {
			free(ret);
			return NULL;
		}
		memcpy(ret->vehicle, info->img[0], info->img_len[0]);
		ret->vlen = info->img_len[0];
	} else {
		ret->vehicle = NULL;
		ret->vlen = 0;
	}

	ret->feature = NULL;
	ret->flen = 0;

	sprintf(ret->vinfo.type, "X99");
	ret->vinfo.color = 'Z';
	ret->vinfo.length = 1000;
	sprintf(ret->vinfo.notype, "Other");
	sprintf(ret->vinfo.no, "%s", info->plate);

	const char *pcolor = "Other";
	if (strlen(info->plate) > 0) {
		switch (info->plate_color) {
		case 0:
			pcolor = "Blue";
			break;
		case 1:
			pcolor = "Black";
			break;
		case 2:
			pcolor = "Yellow";
			break;
		case 3:
			pcolor = "White";
			break;
		default:
			break;
		}
	}
	sprintf(ret->vinfo.nocolor, "%s", pcolor);

	sprintf(ret->dinfo.localtion, "%s", "Empty"); // info->addr_name);
	ret->dinfo.speed = 0;
	sprintf(ret->dinfo.status, "PC_0");
	sprintf(ret->dinfo.time, "%s", timestr);
	ret->next = NULL;
	return ret;
}

void *http_thread(void *arg)
{
	time_t lasttime = time(NULL);
	while (1) {
		printf("a while.\n");
		if (g_reset_flag && strlen(g_device_id) != 0) {	// 当设备ID变化或者第一次启动时
			printf("in reset and regist.\n");
			if (0 == do_regist()) {
				printf("regist success.\n");
				g_reset_flag = 0;
			} else {
				printf("Register failed, sleeping...\n");
				clear_queue();
				sleep(5);
				continue;
			}
		}
		struct timeval begin, end;
		struct analyse_result * pkg = getfrom_queue();

		if (pkg != NULL) {
			printf("send one package.\n");
			gettimeofday(&begin, NULL);
			do_send(pkg);
			release_result(pkg);
			gettimeofday(&end, NULL);
			long diff = (end.tv_sec - begin.tv_sec) * 1000000 + (end.tv_usec - begin.tv_usec);
			printf("time spent for send: %ld\n", diff);
		} else {
			printf("Send queue is NULL\n");
		}

		time_t now = time(NULL);
		if (g_reset_flag == 0 && (now - lasttime) >= http_heart_cycle) {
			printf("Heart beat.\n");
			do_heartbeat();
			lasttime = now;
		}
		usleep(20 * 1000);
	}
	return NULL;
}

/*
 * @brief	将车辆信息包发送给HTTP服务器
 */
int http_client_send(const struct pkg_vehicle *info)
{
	assert(info != NULL);
	if (g_httpthread_tid == (pthread_t) (-1)
			|| strlen(http_server_uri) == 0){
		printf("HTTP client have not setup.\n");
		return -1;
	}

	if (strcmp(info->device_id, g_device_id) != 0) {
		sprintf(g_device_id, "%s", info->device_id);
		g_reset_flag = 1;
	}
	struct analyse_result * pkg = pack_result(info);
	if (pkg == NULL)
		return -1;
	addto_queue(pkg);
	return 0;
}

/*
 * @brief	设置服务器的地址
 * 		当服务器地址发生改变时，也需要通过这个函数进行设置
 * @warning	连接服务器时可能会阻塞, 最大阻塞 5秒
 * @param	server,port指定服务器IP和端口
 * @return	0,成功 -1,失败
 */
int http_client_setup(const char *server, unsigned short port)
{
	assert(server != NULL);
	memset(http_server_uri, 0, MAXBUF);
	snprintf(http_server_uri, MAXBUF, "http://%s:%u", server, port);

	g_reset_flag = 1;

	if (g_httpthread_tid == (pthread_t) -1) {
		pthread_attr_t attr;
		pthread_attr_init(&attr);
		pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
		pthread_create(&g_httpthread_tid, NULL, http_thread, NULL);
		pthread_attr_destroy(&attr);
	}

	return 0;
}
